﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using System.Drawing;
using System.Drawing.Imaging;

namespace LowLevelGraphics.SeamCarving
{
    /// <summary>
    /// Entropy
    /// </summary>
    public sealed class Entropy : Gradient
    {
        private double minE = double.MaxValue, maxE = double.MinValue;

        /// <summary>
        /// To string
        /// </summary>
        /// <returns></returns>
        public override string ToString()
        {
            return "Entropy";
        }

        /// <summary>
        /// Recompute
        /// </summary>
        /// <param name="bmd"></param>
        /// <param name="size"></param>
        public override void Recompute(BitmapData bmd, Size size)
        {
            if (bmd != null)
            {
                double[,] fEnergyMap = new double[bmd.Width, bmd.Height];
                EnergyMap = new byte[bmd.Width, bmd.Height];
                minE = double.MaxValue;
                maxE = double.MinValue;
                for (int x = 0; x < bmd.Width; x++)
                {
                    for (int y = 0; y < bmd.Height; y++)
                    {
                        fEnergyMap[x, y] = GetRealEntropyValue(bmd, size, x, y
                        );
                        minE = Math.Min(fEnergyMap[x, y], minE);
                        maxE = Math.Max(fEnergyMap[x, y], maxE);
                    }
                }
                for (int x = 0; x < bmd.Width; x++)
                    for (int y = 0; y < bmd.Height; y++)
                        EnergyMap[x, y] = (byte)((Normalize(minE, maxE,
                        fEnergyMap[x, y])
                        + Gradient.GetGradientValue(bmd, size, x, y)) / 2)
                        ;
            }
        }

        /// <summary>
        /// Get a real entropy value for a given pixel
        /// </summary>
        /// <param name="bmd"></param>
        /// <param name="size"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        private double GetRealEntropyValue(BitmapData bmd, Size size, int x, int y)
        {
            if (x >= 0 && x < size.Width && y >= 0 && y < size.Height)
            {
                int[] dx = { -1, 0, +1, -1, 0, +1, -1, 0, +1 },
                dy = { -1, -1, -1, 0, 0, 0, +1, +1, +1 };
                double sumE = 0, sumX = 0;
                double[] z = new double[9];
                for (int i = 0; i < z.Length; i++)
                {
                    z[i] = GetEnergyValue(bmd, size, x + dx[i], y + dy[i]);
                    sumX += z[i];
                }
                for (int i = 0; i < z.Length; i++)
                    z[i] /= sumX;
                for (int i = 0; i < z.Length; i++)
                    sumE += z[i] * (z[i] > double.Epsilon ? Math.Log(z[i], 2)
                    : 0);
                return sumE;
            }
            else
            {
                // Out of bounds
                return maxE;
            }
        }

        /// <summary>
        /// Gets an entropy value
        /// </summary>
        /// <param name="bmd"></param>
        /// <param name="size"></param>
        /// <param name="x"></param>
        /// <param name="y"></param>
        /// <returns></returns>
        private byte GetEntropyValue(BitmapData bmd, Size size, int x, int y)
        {
            double energy = GetRealEntropyValue(bmd, size, x, y);
            if (energy < minE || energy > maxE)
            {
                Recompute(bmd, size);
                return EnergyMap[x, y];
            }
            else
                return Normalize(minE, maxE, energy);
        }
    }
}
